package cn.maihe.elg.operation.centers.guogu.channel;

import cn.maihe.elg.operation.centers.guogu.auth.GuoGuCenterAuthConfig;
import cn.maihe.elg.operation.centers.guogu.config.GuoGuCenterInfosConfig;
import cn.maihe.elg.operation.centers.guogu.dto.GuoGuBaseReqDTO;
import cn.maihe.elg.operation.centers.guogu.dto.GuoGuBaseRespDTO;
import cn.maihe.elg.operation.centers.guogu.dto.channel.resp.GuoGuClientCredentialsResp;
import cn.maihe.elg.operation.centers.guogu.dto.channel.resp.GuoGuClientCredentialsRespData;
import cn.maihe.elg.operation.centers.guogu.enums.GuoGuChannelMethod;
import cn.maihe.elg.operation.common.TradeContext;
import cn.maihe.elg.operation.exception.CenterException;
import cn.maihe.elg.operation.model.enums.CenterNoEnum;
import cn.maihe.elg.operation.model.enums.InnerRespCode;
import cn.maihe.elg.operation.service.business.ExchangeCenterService;
import cn.maihe.elg.operation.utils.ElgLogger;
import cn.maihe.elg.operation.utils.IdGenerate;
import cn.maihe.elg.operation.utils.ValidateUtil;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.lang.reflect.ParameterizedType;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @Description 请求南平新点中心基类服务
 * @Author SSX
 * @Date 2025/03/18
 */
@Service
@Slf4j
public abstract class GuoGuBaseChannelService<REQ extends GuoGuBaseReqDTO, RESP extends GuoGuBaseRespDTO> {
    private Cache<String, GuoGuClientCredentialsRespData> clientCredentialsCache = CacheBuilder.newBuilder().expireAfterWrite(60*8, TimeUnit.SECONDS).build();
    private final static String CLIENT_CREDENTIALS = "CLIENT_CREDENTIALS";

    @Resource
    private RestTemplate restTemplate;
    @Resource
    protected GuoGuCenterInfosConfig guoGuEpointCenterInfosConfig;
    @Resource
    private ExchangeCenterService exchangeCenterService;

    private final ObjectMapper objectMapper = new ObjectMapper();
    protected final Class<RESP> respClass;


    public GuoGuBaseChannelService() {
        respClass = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    /**
     * 设置公共字段：method&timestamp
     */
    protected REQ buildReq(REQ req, GuoGuCenterAuthConfig authConfig) {
        return req;
    }

    protected String getAccessToken(String clientId,GuoGuCenterAuthConfig authConfig) {
        GuoGuClientCredentialsRespData clientCredentials;
        String cacheKey = CLIENT_CREDENTIALS+"_"+clientId;
        try {
            clientCredentials = clientCredentialsCache.getIfPresent(cacheKey);
            if (Objects.isNull(clientCredentials)) {
                // 1. 构建表单数据
                MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
                // 添加表单字段
                body.add("grant_type", "client_credentials");

                Map<String, Object> headerMap = new HashMap<>();
                headerMap.put("Authorization", "Basic "+Base64.encodeBase64String((clientId+":"+ IdGenerate.get32UUID()).getBytes(StandardCharsets.UTF_8)));
                headerMap.put("method","token");

                //请求渠道服务
                String reqUrl = this.buildReqUrl(clientId,authConfig);
                try {
                    ElgLogger.info(log, l -> l.info("token服务 请求报文headerMap：{}", JSON.toJSONString(headerMap)));
                    HttpHeaders headers = new HttpHeaders();
                    headerMap.forEach((k, v) -> {
                        headers.add(k, (String) v);
                    });
                    headers.setContentType(MediaType.MULTIPART_FORM_DATA);
                    HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(body, headers);
                    ElgLogger.info(log, l -> l.info("token服务 请求报文 request：{}", JSON.toJSONString(request)));
                    ResponseEntity<GuoGuClientCredentialsResp> responseEntity = restTemplate.postForEntity(reqUrl, request, GuoGuClientCredentialsResp.class);
                    if (responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful()) {
                        //正常响应业务处理
                        GuoGuClientCredentialsResp resp = responseEntity.getBody();
                        ElgLogger.info(log, l -> l.info("token服务  响应报文 resp:{}",JSON.toJSONString(resp)));
                        if("200".equals(resp.getCode())){
                            clientCredentials = resp.getData();
                            verifyRespData(clientCredentials);
                            String access_token = clientCredentials.getAccess_token();
                            String expiresIn = clientCredentials.getExpires_in();
                            ElgLogger.info(log, l -> l.info("token服务  响应报文 access_token:{},expires_in:{}",access_token,expiresIn));
                        }else {
                            throw new RuntimeException("token服务 获取失败");
                        }

                    }else {
                        throw new RuntimeException("响应报文为空或http状态非成功");
                    }
                } catch (Exception e) {
                    CenterException ce = buildCException(InnerRespCode.FAILURE, e, "请求响应异常:");
                    ElgLogger.error(log, l -> l.error("token服务 请求响应异常：{}",ce.getMessage()));
                }
                clientCredentialsCache.put(cacheKey, clientCredentials);
            }
        } catch (Exception e) {
            ElgLogger.error(log, l -> l.error("getAccessToken error", e));
            throw CenterException.exception("tgetAccessToken error", e);
        }
        return clientCredentials.getAccess_token();
    }

    private void verifyRespData(GuoGuClientCredentialsRespData clientCredentials) {
        try {
            ValidateUtil.validate(clientCredentials);
        } catch (Exception e) {
            throw CenterException.of(InnerRespCode.FAILURE_PARAM, "响应数据错误", e);
        }
    }

    /**
     * 实际请求url
     */
    private String buildReqUrl(String clientId,GuoGuCenterAuthConfig authConfig) {
        Map<String, GuoGuCenterInfosConfig.ClientInfo> clientInfos = authConfig.getCenterInfo().getClientInfos();
        GuoGuCenterInfosConfig.ClientInfo clientInfo = clientInfos.get(clientId);
        return clientInfo.getUrl().trim();
        //String reUrl = null;
        //String url = authConfig.getCenterInfo().getUrl().trim();
//        if (url.endsWith("/")) {
//            reUrl = url + methodEnum().getMethod();
//        } else {
//            reUrl = url + "/" + methodEnum().getMethod();
//        }
//        //若有特殊配置指定地址则已指定地址为准
//        if (methodEnum() == GuoGuChannelMethod.baohannotice && StringUtils.isNotBlank(authConfig.getCenterInfo().getBaoHanNoticeUrl())) {
//            reUrl = authConfig.getCenterInfo().getBaoHanNoticeUrl();
//        } else if (methodEnum() == GuoGuChannelMethod.cancel_notify && StringUtils.isNotBlank(authConfig.getCenterInfo().getQuitNoticeUrl())) {
//            reUrl = authConfig.getCenterInfo().getQuitNoticeUrl();
//        } else if (methodEnum() == GuoGuChannelMethod.claim_notify && StringUtils.isNotBlank(authConfig.getCenterInfo().getClaimsResultNoticeUrl())) {
//            reUrl = authConfig.getCenterInfo().getClaimsResultNoticeUrl();
//        } else if (methodEnum() == GuoGuChannelMethod.uploadbaohanfile && StringUtils.isNotBlank(authConfig.getCenterInfo().getUploadFileUrl())) {
//            reUrl = authConfig.getCenterInfo().getUploadFileUrl();
//        } else if (methodEnum() == GuoGuChannelMethod.baohanfilecompletenotice && StringUtils.isNotBlank(authConfig.getCenterInfo().getFileCompleteUrl())) {
//            reUrl = authConfig.getCenterInfo().getFileCompleteUrl();
//        }

        //return reUrl;
    }

    public RESP doRequest(String clientId,CenterNoEnum centerNoEnum, REQ req) {
        return this.doRequest(clientId,centerNoEnum, req, null);
    }

    /**
     * @param req     参与签名验签数据
     * @param attrMap 不参与签名验签附加属性
     * @return
     */
    public RESP doRequest(String clientId,CenterNoEnum centerNoEnum, REQ req, Map<String, Object> attrMap) {

        GuoGuCenterAuthConfig authConfig = guoGuEpointCenterInfosConfig.getAuthConfig(centerNoEnum);

        //构建上下文
        TradeContext context = new TradeContext(req, attrMap);
        try {
            //接口公共参数设置
            req = buildReq(req, authConfig);

            //请求参数校验
            verifyParam(context);
        } catch (Exception e) {
            CenterException ce = buildCException(InnerRespCode.FAILURE_PARAM, e, "参数错误:");
            ElgLogger.error(log, l -> l.error("traceId={} => 中心[{}][{}]服务 参数校验失败：{}", context.getTraceId(),centerNoEnum.getCenterNo(), methodEnum().getDesc(), ce.getMessage()));
            throw ce;
        }

        //加密请求参数
        //String clearBizReqBody = this.toJsonString(req);
        String clearBizReqBody = JSON.toJSONString(req);
        String encryptAndB64ReqBody;
        //业务数据sm2公钥加密
        try {
            encryptAndB64ReqBody = authConfig.getGuoGuEncryption().encryptForGuoGu(clientId,clearBizReqBody);
        } catch (Exception e) {
            CenterException ce = buildCException(InnerRespCode.FAILURE_PARAM, e, "加密失败:");
            ElgLogger.error(log, l -> l.error("traceId={} => 中心[{}][{}]服务 参数加密失败：{}", context.getTraceId(), centerNoEnum.getCenterNo(), methodEnum().getDesc(), ce.getMessage()));
            throw ce;
        }


        Map<String, Object> headerMap = new HashMap<>();
        headerMap.put("Authorization","Bearer "+getAccessToken(clientId,authConfig));
        headerMap.put("method",methodEnum().getMethod());
        try {
            String signature = authConfig.getGuoGuAuthorization().signature(clientId,clearBizReqBody);
            headerMap.put("signature",signature);
        } catch (Exception e) {
            CenterException ce = buildCException(InnerRespCode.FAILURE, e, "签名异常:");
            ElgLogger.error(log, l -> l.error("traceId={} => 中心[{}][{}]服务 签名异常：{}", context.getTraceId(), centerNoEnum.getCenterNo(), methodEnum().getDesc(), ce.getMessage()));
            throw ce;
        }

        //请求渠道服务
        String reqUrl = this.buildReqUrl(clientId,authConfig);
        try {
            REQ finalReq = req;
            ElgLogger.info(log, l -> l.info("traceId={} => 中心[{}][{}]服务 请求报文：{}", context.getTraceId(),centerNoEnum.getCenterNo(), methodEnum().getDesc(), this.toJsonString(finalReq)));
            ElgLogger.info(log, l -> l.info("traceId={} => 中心[{}][{}]服务 请求地址：{}", context.getTraceId(),centerNoEnum.getCenterNo(), methodEnum().getDesc(), reqUrl));
            HttpHeaders headers = new HttpHeaders();
            headerMap.forEach((k, v) -> {
                headers.add(k, (String) v);
            });
            headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
            headers.setContentType(MediaType.TEXT_PLAIN);
            HttpEntity<String> request = new HttpEntity<>(encryptAndB64ReqBody, headers);
            ElgLogger.info(log, l -> l.info("traceId={} => 中心[{}][{}]服务 请求request：{}", context.getTraceId(),centerNoEnum.getCenterNo(), methodEnum().getDesc(), JSON.toJSONString(request)));
            ResponseEntity<String> responseEntity = restTemplate.postForEntity(reqUrl, request, String.class);
            if (responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful()) {
                //正常响应业务处理
                String encrypt = authConfig.getGuoGuEncryption().decrypt(responseEntity.getBody());
                RESP resp = JSON.parseObject(encrypt,respClass);
                ElgLogger.info(log, l -> l.info("traceId={} <= 中心[{}][{}]服务 响应报文：{}", context.getTraceId(), centerNoEnum.getCenterNo(),methodEnum().getDesc(), this.toJsonString(resp)));
                return resp;
            }
            throw new RuntimeException("响应报文为空或http状态非成功");
        } catch (Exception e) {
            CenterException ce = buildCException(InnerRespCode.FAILURE, e, "请求响应异常:");
            ElgLogger.error(log, l -> l.error("traceId={} <= 中心[{}][{}]服务 请求响应异常：{}", context.getTraceId(),centerNoEnum.getCenterNo(), methodEnum().getDesc(), ce.getMessage()));
            throw ce;
        }


    }

    protected CenterException buildCException(InnerRespCode innerRespCode, Exception exception, String message) {
        CenterException ce;
        if (exception instanceof SocketTimeoutException) {
            ce = CenterException.timeout("超时:" + message.concat(exception.getMessage()), exception);
        } else if (exception instanceof CenterException) {
            ce = (CenterException) exception;
        } else {
            if (exception != null) {
                ce = CenterException.exception(message.concat(exception.getMessage()), exception);
            } else {
                ce = CenterException.of(innerRespCode, message, exception);
            }
        }
        return ce;
    }

    private void verifyParam(TradeContext<REQ, RESP> context) {
        ElgLogger.debug(log, l -> l.debug("traceId={} => [{}]服务 请求参数：{}", context.getTraceId(), methodEnum().getDesc(), toJsonString(context.getReq())));
        if (StringUtils.isBlank(methodEnum().getMethod())) {
            throw CenterException.of(InnerRespCode.FAILURE_PARAM, "method参数不能为空", null);
        }
        try {
            ValidateUtil.validate(context.getReq());
        } catch (Exception e) {
            throw CenterException.of(InnerRespCode.FAILURE_PARAM, "请求参数错误", e);
        }
    }

    /**
     * 渠道方法名
     *
     * @return
     */
    protected abstract GuoGuChannelMethod methodEnum();


    private String timestamp() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

    private String toJsonString(Object body) {
        try {
            return objectMapper.writeValueAsString(body);
        } catch (JsonProcessingException e) {
            throw CenterException.exception("转换json字符串异常", e);
        }
    }

    protected CenterNoEnum getCenterNoEnumById(Long centerId) {
        return exchangeCenterService.getCenterNoEnumById(centerId);
    }
}
